package com.gitee.wsl.mathematics.geometry.d2.contour.segment.fcurve

import com.gitee.wsl.mathematics.geometry.d2.contour.ShapeContour
import com.gitee.wsl.mathematics.geometry.d2.contour.segment.Segment2D
import com.gitee.wsl.mathematics.vector.vec2.Vec2f
import kotlinx.serialization.Serializable
import kotlin.math.abs


/**
 * Fcurve class
 */
/*

data class FCurve(val segments: List<Segment2D>) {

    */
/**
     * Reverse the fcurve
     *//*

    fun reverse(): FCurve {
        val d = duration
        val t = buildTransform {
            translate(d, 0.0)
            scale(-1.0, 1.0)
        }
        return FCurve(segments.map { it.reverse.transform(t) })
    }

    val bounds by lazy {
        segments.map { it.bounds }.bounds
    }
    val min: Double
        get() {
            if (segments.isEmpty()) return 0.0 else return bounds.position(0.0, 0.0).y
        }

    val max: Double
        get() {
            if (segments.isEmpty()) return 0.0 else return bounds.position(1.0, 1.0).y
        }

    */
/**
     * Change the duration of the Fcurve
     *//*

    fun changeSpeed(speed: Double): FCurve {
        val c = if (speed < 0.0) reverse() else this
        return if (speed == 1.0) c else {
            val t = buildTransform {
                scale(1.0 / speed, 1.0)
            }
            FCurve(c.segments.map { it.transform(t) })
        }
    }

    */
/**
     * Create a sampler or function from the Fcurve
     *//*

    fun sampler(normalized: Boolean = false): (Double) -> Double {
        var cachedSegment: Segment2D? = null
        if (!normalized) {
            return { t ->
                val r = valueWithSegment(t, cachedSegment)
                cachedSegment = r.second
                r.first
            }
        } else {
            val d = duration
            return { t ->
                val r = valueWithSegment(t * d, cachedSegment)
                cachedSegment = r.second
                r.first
            }
        }
    }

    */
/**
     * The unitless duration of the Fcurve
     *//*

    val duration: Double
        get() {
            return if (segments.isEmpty()) {
                0.0
            } else {
                end - start
            }
        }

    */
/**
     * The unitless start position of the Fcurve
     *//*

    val start: Double
        get() {
            return if (segments.isEmpty()) {
                0.0
            } else {
                segments.first().start.x
            }
        }

    */
/**
     * The unitless end position of the Fcurve
     *//*

    val end: Double
        get() {
            return if (segments.isEmpty()) {
                0.0
            } else {
                segments.last().end.x
            }
        }


    */
/**
     * Evaluate the Fcurve at [t]
     * @param segment an optional segment that can be used to speed up scanning for the relevant segment
     * @see valueWithSegment
     *//*

    fun value(t: Double, segment: Segment2D? = null): Double = valueWithSegment(t, segment).first

    */
/**
     * Evaluate the Fcurve at [t]
     * @param segment an optional segment that can be used to speed up scanning for the relevant segment
     *//*

    fun valueWithSegment(t: Double, cachedSegment: Segment2D? = null): Pair<Double, Segment2D?> {
        if (cachedSegment != null) {
            if (t >= cachedSegment.start.x && t < cachedSegment.end.x) {
                return Pair(cachedSegment.yForX(t), cachedSegment)
            }
        }

        if (segments.isEmpty()) {
            return Pair(0.0, null)
        }
        if (t <= segments.first().start.x) {
            val segment = segments.first()
            return Pair(segment.start.y, segment)
        } else if (t > segments.last().end.x) {
            val segment = segments.last()
            return Pair(segment.end.y, segment)
        } else {
            val segmentIndex = segments.binarySearch {
                if (t < it.start.x) {
                    1
                } else if (t > it.end.x) {
                    -1
                } else {
                    0
                }
            }
            val segment = segments.getOrNull(segmentIndex)
            return Pair(segment?.yForX(t) ?: 0.0, segment)
        }
    }

    */
/**
     * Return a list of contours that can be used to visualize the Fcurve
     *//*

    fun contours(scale: Vec2f = Vec2f(1.0, -1.0), offset: Vec2f = Vec2f.ZERO): List<ShapeContour> {
        var active = mutableListOf<Segment2D>()
        val result = mutableListOf<ShapeContour>()

        for (segment in segments) {

            val tsegment = segment.transform(
                buildTransform {
                    translate(offset)
                    scale(scale.x, scale.y)
                }
            )

            if (active.isEmpty()) {
                active.add(tsegment)
            } else {
                val dy = abs(active.last().end.y - tsegment.start.y)
                if (dy > 1E-3) {
                    result.add(ShapeContour.fromSegments(active, false))
                    active = mutableListOf()
                }
                active.add(tsegment)
            }
        }
        if (active.isNotEmpty()) {
            result.add(ShapeContour.fromSegments(active, false))
        }
        return result
    }
}

*/
/**
 * Fcurve builder
 *//*

class FCurveBuilder {
    val segments = mutableListOf<Segment2D>()
    var cursor = Vec2f(0.0, 0.0)

    var path = ""

    fun moveTo(y: Double, relative: Boolean = false) {
        cursor = if (!relative) cursor.copy(y = y) else cursor.copy(y = cursor.y + y)
        path += "${if (relative) "m" else "M"}$y"
    }

    fun lineTo(x: Double, y: Double, relative: Boolean = false) {
        val r = if (relative) 1.0 else 0.0
        segments.add(Segment2D(cursor, Vec2f(x + cursor.x, y + cursor.y * r)))
        cursor = Vec2f(cursor.x + x, cursor.y * r + y)
        path += "${if (relative) "l" else "L"}$x,$y"
    }

    fun curveTo(
        x0: Double, y0: Double,
        x: Double, y: Double,
        relative: Boolean = false
    ) {
        val r = if (relative) 1.0 else 0.0
        segments.add(
            Segment2D(
                cursor,
                Vec2f(cursor.x + x0, cursor.y * r + y0),
                Vec2f(cursor.x + x, cursor.y * r + y)
            )
        )
        cursor = Vec2f(cursor.x + x, cursor.y * r + y)
        path += "${if (relative) "q" else "Q"}$x0,$y0,$x,$y"
    }

    fun curveTo(
        x0: Double, y0: Double,
        x1: Double, y1: Double,
        x: Double, y: Double, relative: Boolean = false
    ) {
        val r = if (relative) 1.0 else 0.0
        segments.add(
            Segment2D(
                cursor,
                Vec2f(cursor.x + x0, cursor.y * r + y0),
                Vec2f(cursor.x + x1, cursor.y * r + y1),
                Vec2f(cursor.x + x, cursor.y * r + y)
            ).scaleTangents()
        )
        cursor = Vec2f(cursor.x + x, cursor.y * r + y)
        path += "${if (relative) "c" else "C"}$x0,$y0,$x,$y"
    }

    fun continueTo(x: Double, y: Double, relative: Boolean = false) {
        val r = if (relative) 1.0 else 0.0

        if (segments.isNotEmpty()) {
            val lastSegment = segments.last()
            val lastDuration = lastSegment.end.x - lastSegment.start.x
            val outTangent = if (segments.last().linear) lastSegment.end else segments.last().control.last()
            val outPos = lastSegment.end
            val d = outPos - outTangent
            //val dn = d.normalized
            val ts = 1.0// x / lastDuration
            segments.add(
                Segment2D(
                    cursor,
                    cursor + d * ts,
                    Vec2f(cursor.x + x, cursor.y * r + y)
                ).scaleTangents()
            )
        } else {
            segments.add(
                Segment2D(cursor,
                    Vec2f(cursor.x + x, cursor.y * r + y)).quadratic
            )
        }
        cursor = Vec2f(cursor.x + x, cursor.y * r + y)
        path += "${if (relative) "t" else "T"}$x,$y"
    }

    fun continueTo(x1: Double, y1: Double, x: Double, y: Double, relative: Boolean = false) {
        val r = if (relative) 1.0 else 0.0
        val lastSegment = segments.last()
        val outTangent = if (lastSegment.linear) lastSegment.position(0.5) else segments.last().control.last()
        val dx = cursor.x - outTangent.x
        val dy = cursor.y - outTangent.y
        segments.add(
            Segment2D(
                cursor,
                Vec2f(cursor.x + dx, cursor.y + dy),
                Vec2f(cursor.x + x1, cursor.y * r + y1),
                Vec2f(cursor.x + x, cursor.y * r + y)
            ).scaleTangents()
        )
        cursor = Vec2f(cursor.x + x, cursor.y * r + y)
        path += "${if (relative) "s" else "S"}$x1,$y1,$x,$y"
    }

    fun hold(x: Double, relative: Boolean = true) {
        if (relative) {
            lineTo(x, cursor.y)
        } else {
            require(segments.isEmpty()) { "absolute hold (H $x) is only allowed when used as first command" }
            cursor = cursor.copy(x = x)
        }
        path += "h$x"
    }

    */
/**
     * build the Fcurve
     *//*

    fun build(): FCurve {
        return FCurve(segments)
    }
}

*/
/**
 * build an Fcurve
 * @see FCurveBuilder
 *//*

fun fcurve(builder: FCurveBuilder.() -> Unit): FCurve {
    val fb = FCurveBuilder()
    fb.builder()
    return fb.build()
}

*/
/**
 * Split an Fcurve string in to command parts
 *//*

fun fCurveCommands(d: String): List<String> {
    val svgCommands = "mMlLqQsStTcChH"
    val number = "0-9.\\-E%"

    return d.split(Regex("(?:[\t ,]|\r?\n)+|(?<=[$svgCommands])(?=[$number])|(?<=[$number])(?=[$svgCommands])"))
        .filter { it.isNotBlank() }
}*/
